'''人类也能简单使用的kfol计算器'''

### 在这里设定基本数据

### 计算器设置开始
## 计算器目录与exe文件,如果是32位请设定为'kfol2.exe'
KFOL_PATH = r'E:\PC\kfol2_2.9.0.1'
KFOL_EXE = 'kfol2_64.exe'
### 计算器设置结束

### 账户资料设置开始
## 'in_file'是kfol计算器使用的.in配置文件,单个账户的可直接设置为kfol.in
## 'total_point'是加点总数值,'base_hp'是体质加点为0时的hp值,打开争夺页面
## 用显示的最大hp减去20*体质加点计算,
## 'weapons'与'armors'是自己记录的配置文件的武器与防具,自定义一个名称,
## 然后指定[在配置文件中的编号, id].
## 看我的两个账户的设定:setycyas与工藤駆
SETYCYAS = {
  'in_file': 'kfol_setycyas.in',
  'total_point': 795,
  'base_hp': 8820-170*20,
  'weapons': {
    "火杖0":[0, "#61308105"], "火剑0":[1, "#61308163"], 
    "火杖1":[2, "#61316407"], "雷杖0":[3, "#61319081"],
    "雷剑0":[4, "#61319267"], "风剑0":[5, "#61405459"], 
    "火弓0":[6, "#61438832"], "风杖0":[7, "#61508531"], 
    "火弓1":[8, "#61512762"]
  },
  'armors': {
    "仇甲0":[0, "#25721065"], "冰甲0":[1, "#61306325"],
    "仇甲1":[2, "#61519118"], "冰甲1":[3, "#61518585"],
    "仇甲2":[4, "#61519118"]
  }
}
KUDOU_KAKERU = {
  'in_file': 'kfol_kakeru.in',
  'total_point': 200,
  'base_hp': 960,
  'weapons': {
    "技暴弓0":[0, "#61351736"], "技剑0":[1, "#56290895"]
  },
  'armors': {
    "血防盾甲0":[0, "#61352729"], "防盾反甲0":[1, "#56290866"]
  }
}

### 账户资料设置结束

import subprocess
import re
import os
import shutil
import sys

class Kfol:

  ## 指定计算器路径,exe文件,账户,
  ## 使用武器,护甲,计算步长,计算层,开始计算的点进行初始化
  def __init__(self, kfol_path, kfol_exe, 
    account, weapon, armor, step, floor, start_point):
    ## 保存参数
    self.kfol_path = kfol_path
    self.kfol_exe = kfol_exe
    self.account = account # 账户
    self.weapon = weapon # 武器
    self.armor = armor # 护甲
    self.step = step # 搜索步长
    self.floor = floor # 计算层数
    ## 计算过程使用的变量
    self.cur_point = start_point # 当前最佳点
    ## 自定义的最大寻找次数
    self.MAX_SEARCH = 10
    ## 复制.in文件
    if account['in_file'] != 'kfol.in':
      shutil.copyfile(os.path.join(sys.path[0], account['in_file']),
        os.path.join(sys.path[0], 'kfol.in'))
      print('复制配置文件成功!')
    else:
      print('目前设定不需要复制配置文件!')
    return
  
  ## 获取一个点的胜率,返回一个%浮点数,若计算失败返回-100.0
  def getPointWinrate(self, point):
    ## Message to send to kfol2
    hp = self.account['base_hp']+20*point[1]
    weapon = self.account['weapons'][self.weapon][0]
    armor = self.account['armors'][self.armor][0]
    msg = 'b {floor} {hp} {point[0]} {point[1]} {point[2]} {point[3]} '
    msg += '{point[4]} {point[5]} {weapon} {armor}\r\n'
    msg = msg.format(
      floor = self.floor, hp = hp, point = point, 
      weapon = weapon, armor = armor
    )
    print("Testing:")
    print(msg[:-2])
    ## 打开计算器
    proc = subprocess.Popen([os.path.join(self.kfol_path, 
      self.kfol_exe)], 
      stdin = subprocess.PIPE, 
      stdout = subprocess.PIPE
    )
    ## Send message(bytes) to kfol2, and get winrate
    respBytes = proc.communicate(input = msg.encode())[0]
    resp = respBytes.decode().upper()
    matches = re.findall(r'WIN RATE SUMMARY = (\d+\.\d+)', resp)
    ## Return
    result = -100.0
    if matches:
      result = float(matches[-1])
    print('result is:', str(result)+'%\n')
    return result
  
  ## 获取一个点的附近的点集,返回一个列表
  def getNearPoints(self, point):
    result = []
    ## 如果当前最佳点的分配已经满了,则获取所有的两项分配的一增一减点集合
    if (sum(point) == self.account['total_point']):
      ## i是增加项,j是减少项
      for i in range(6):
        for j in range(6):
          ## 如果增加项就是减少项,或减少项已经是1不能再减少,跳过
          if (i == j) or (point[j] == 1):
            continue
          # 计算步长为指定步长,或可以变动的最大数值
          step = min(self.step, point[j]-1)
          newPoint = point.copy()
          newPoint[i] += step
          newPoint[j] -= step
          result.append(newPoint)
    ## 如果当前最佳点的分配未满,则获取所有单项增加的点
    else:
      step = min(self.step, self.account['total_point']-sum(self.cur_point))
      print(step)
      for i in range(6):
        newPoint = point.copy()
        newPoint[i] += step
        result.append(newPoint)
    ## 返回
    return result
  
  ## 优化一个点,在指定点附近的点集找出最大优化,返回最大优化的点与其胜率,
  ## 返回的也可能是给定点自己,这时表示已经不能进行附近优化
  ## winrate是指定的点的胜率,如果传入可以少计算一次,
  ## 如果传入负数则表示还没有计算,需要重新算一次
  def optimizePoint(self, point, winrate = -100.0):
    ## 指定当前点与胜率
    cur_point = point
    if winrate < 0:
      cur_winrate = self.getPointWinrate(cur_point)
    else:
      cur_winrate = winrate
    ## 获取附近点集,然后计算胜率找出最大值
    nearPoints = self.getNearPoints(point)
    for near_point in nearPoints:
      near_winrate = self.getPointWinrate(near_point)
      if near_winrate > cur_winrate:
        cur_point = near_point
        cur_winrate = near_winrate
    ## 返回
    return (cur_point, cur_winrate)
    
  ## 连续寻找最大优化,不断找附近的最大优化点,
  ## 直到附近不再有优化,或次数超过指定数值.返回优化点,胜率
  def getMaxOptimizePoint(self, point):
    cur_point = point
    cur_winrate = -100.0
    for i in range(self.MAX_SEARCH):
      new_point, new_winrate = self.optimizePoint(cur_point, cur_winrate)
      print('第 {0} 次优化完成,最佳点: {1} 胜率: {2}\n'.format(
        i+1, new_point, new_winrate
      ))
      if cur_point == new_point:
        cur_point, cur_winrate = new_point, new_winrate
        break
      else:
        cur_point, cur_winrate = new_point, new_winrate
    return cur_point, cur_winrate
    
  ## Main
  def main(self):
    print('开始进行优化,配置文件是:', self.account['in_file'])
    print('计算目标是:{weapon}, {armor}, {floor}层, 步长{step}'.format(
      weapon = self.weapon, armor = self.armor, floor = self.floor, 
      step = self.step))
    x = input('确认后输入回车开始,任意键加回车退出')
    if(len(x) == 0):
      self.getMaxOptimizePoint(self.cur_point)
    return
    

if __name__ == '__main__':
  ### 测试设置开始
  
  account = SETYCYAS 
  weapon = "雷剑0"
  armor = "冰甲0"
  step = 20
  floor = 150
  
  # 火弓0冰甲0,140层,100胜率,150以下可用
  #start_point = [1, 170, 1, 310, 1, 307] 
  
  #雷剑0冰甲0,155层,胜率63,150与150以上用
  start_point = [1, 30, 1, 655, 1, 107] 
  
  ### 测试设置结束
  
  kf = Kfol(KFOL_PATH, KFOL_EXE, account, weapon, armor, 
    step, floor, start_point)
  kf.main()
  input('优化完成,输入回车结束')